#include<iostream>
#include<sstream>
#include<fstream>
#include<cstdlib>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;

// CONFIGURATION SECTION
bool decorate_slave_header = true;
//bool decorate_slave_header = false;
// END CONFIGURATION

map<string,int> rowindex_map;
string ran;
//long int ran;
int totalrows=0;
//int slave_tables = 2;
//int * slave_matrix;
int * total_fields;
vector<string> rslist;
string * slave_data;
string * headers;
vector<string> active_key;
bool use_header = 1;
int key_dim=1;
int file_args;
string masterfile;
const char * MASTERFILE = "master";

string clip_key_token(string input){
  istringstream iss(input);
  string tok;
  for(int i=0;i<key_dim;++i) iss>>tok;
  ostringstream oss;
  int i=0;
  while(iss>>tok){
    if(i) oss<<"\t";
    oss<<tok;
    ++i;
  }
  return oss.str();
}

string sort_key(){
  sort(active_key.begin(),active_key.end());
  //cerr<<"Sorted\n";
  ostringstream oss;
  int field=0;
  for(vector<string>::iterator it = active_key.begin();
  it!=active_key.end();it++){
    if (field>0) oss<<"\t";
    oss<<*it;
    ++field;
  }
  return oss.str();
}

void load_master(const char * file){
  double start = clock();
  istream * is = &cin;
  ifstream * p_ifs = NULL;
  ofstream temp_ofs;
  bool write_temp_master = (file[0]=='-');
  if (!write_temp_master){
    is = new ifstream(file);
    p_ifs = static_cast<ifstream * >(is);
    if (!p_ifs->is_open()){
      cerr<<"Cannot open "<<file<<endl;
      exit(1);
    }
  }else{
    cerr<<"Masterfile is STDIN\n";
    masterfile = MASTERFILE;
    write_temp_master = true;
    temp_ofs.open(masterfile.data());
    if(!temp_ofs.is_open()){
      cerr<<"Could not write temp master file\n";
      exit(1);
    }
  }
  if (use_header) {
    getline(*is,headers[0]);
    temp_ofs<<headers[0]<<endl; 
    //cerr<<headers[0]<<endl; 
  }
  string line;
  while(getline(*is,line)){
    temp_ofs<<line<<endl;
    //cerr<<line<<endl;
    istringstream iss(line);
    for(int i=0;i<key_dim;++i){
      iss>>active_key[i];
      //cerr<<"active key : "<<active_key[i]<<endl;
    }
    string rs = sort_key();
    rslist.push_back(rs);
    //cerr<<"Pushing back "<<rs<<endl;
    rowindex_map[rs]=totalrows++;
  }  
  if (!write_temp_master){
    p_ifs->close();
    delete p_ifs;
    temp_ofs.close();
  }
  cerr<<"Loaded master "<<file<<" with "<<totalrows<<" total rows in "<<(clock()-start)/CLOCKS_PER_SEC<<" seconds.\n";
}

void load_slave(int file_id,const char * file, int & fields){
  double start = clock();
  istream * is = &cin;
  ifstream * p_ifs = NULL;
  if (file[0]!='-'){
    is = new ifstream(file);
    p_ifs = static_cast<ifstream * >(is);
    if (!p_ifs->is_open()){
      cerr<<"Cannot open "<<file<<endl;
      exit(1);
    }
  }else{
    cerr<<"Slave "<<file_id<<" is STDIN\n";
  }
  fields = 0;
  string orig_header,token;
  //getline(ifs,headers[file_id+1]);
  if (use_header){
    getline(*is,orig_header);
    if(decorate_slave_header){
      istringstream iss_header(orig_header);
      // decorate each header token with the name of slave file
      ostringstream oss_header;
      int i=0;
      while(iss_header>>token){
        ostringstream oss_token;
        oss_token<<file<<"."<<token;
        if (i) oss_header<<"\t";
        oss_header<<oss_token.str();
        ++i;
        //++fields;
      }
      headers[file_id+1] = oss_header.str(); 
    }else{
      //istringstream iss_header(orig_header);
      //while(iss_header>>token) {
      //  ++fields;
      //}
      headers[file_id+1] = orig_header;
    }
  }
  //fields-=key_dim;
  string line,rs;
  int linenum=0;
  while(getline(*is,line)){
    istringstream iss(line);
    for(int i=0;i<key_dim;++i){
      iss>>active_key[i];
    }
    if (linenum==0){
      string token;
      while(iss>>token) ++fields;
    }
    rs = sort_key();
    map<string,int>::iterator it = rowindex_map.find(rs);
    if (it!=rowindex_map.end()){
      //cerr<<"Found at "<<it->second<<endl;
      slave_data[it->second] = line;
    }
    ++linenum;
  }  
  if (file[0]!='-'){
    p_ifs->close();
    delete p_ifs;
  }
  cerr<<"Loaded slave "<<file_id<<" in "<<(clock()-start)/CLOCKS_PER_SEC<<" seconds.\n";
}

void print_padded(int fields,int suffix){
  //cerr<<"Suffix: "<<suffix<<endl;
  double start = clock();
  ostringstream oss;
  oss<<ran<<"."<<suffix;
  cerr<<"Printing padded file "<<oss.str()<<" with "<<totalrows<<" total rows and "<<fields<<" fields."<<endl;
  ofstream ofs(oss.str().data());
  for(int i=0;i<totalrows;++i){
    if (slave_data[i].compare("")==0){
      ofs<<rslist[i];
      for(int i=0;i<fields;++i){
        ofs<<"\t";
        ofs<<"NULL";
      }
      ofs<<endl;
      //cerr<<"printing nothing\n";
    }else{
      //cerr<<"printing something\n";
      ofs<<slave_data[i]<<endl;
    }
  }
  ofs.close();
  cerr<<"Printed padded slave in "<<(clock()-start)/CLOCKS_PER_SEC<<" seconds.\n";
}

void paste_all(){
  double start = clock();
  ifstream * ifs = new ifstream[file_args];
  ifs[0].open(masterfile.data());
  for(int arg=1;arg<file_args;++arg){
    int slave_id = arg-1;
    ostringstream oss;
    oss<<ran<<"."<<slave_id;
    cerr<<"Opening "<<oss.str().data()<<endl;
    ifs[arg].open(oss.str().data());
  }
  string line;
  if(use_header){
    for(int j=0;j<file_args;++j){
      string header;
      if (j) {
        header = clip_key_token(headers[j]);
        cout<<"\t";
      }else{
        header = headers[j];
      }
      cout<<header;
    }
    cout<<endl;
    getline(ifs[0],line);
  }
  //cerr<<"totalrows: "<<totalrows<<endl;
  for(int i=0;i<totalrows;++i){
    for(int j=0;j<file_args;++j){
      if (j) cout<<"\t";
      string line;
      getline(ifs[j],line);
      if(j){
        line = clip_key_token(line);
      }
      cout<<line;
    }
    cout<<endl;
  }
  for(int j=0;j<file_args;++j){
     ifs[j].close();
  }
  for(int arg=0;arg<file_args-1;++arg){
    ostringstream oss;
    oss<<ran<<"."<<arg;
    if (remove(oss.str().data())==0){
      cerr<<"Successfully deleted "<<oss.str().data()<<endl;
    }else{
      cerr<<"Failed to delete "<<oss.str().data()<<endl;
    }
  }
  cerr<<"Concatenated all files in "<<(clock()-start)/CLOCKS_PER_SEC<<" seconds.\n";
} 


int main(int argc,char * argv[]){
  if (argc<3){
    cerr<<"Usage: <options> <masterfile (e.g. METAL output)> <slave file (e.g. annotations)> <study table 1><study table 2>..<study table n>\n";
    cerr<<"Options:\n";
    cerr<<"-h (use-header) [1|0]\n";
    cerr<<"-k (key-dim >0)\n";
    cerr<<"-d (decorate-header) [1|0]\n";
    cerr<<"Notes:\n";
    cerr<<" 1) Files should be in tab delimited format\n";
    cerr<<" 2) The first column should contain key\n";
    cerr<<endl;
    return -1;
  }
  double start = clock();
  int arg=1;
  vector<string> filename_vec;
  while(arg<argc){
    if (argv[arg][0]=='-'){
      switch(argv[arg][1]){
      case 'h':
        use_header = atoi(argv[++arg]);
        cerr<<"Use header set to "<<use_header<<"\n";
        break;
      case 'k':
        key_dim = atoi(argv[++arg]);
        cerr<<"Key dimension set to "<<key_dim<<"\n";
        break;
      case 'd':
       decorate_slave_header = atoi(argv[++arg]);
       cerr<<"Decorate slave header set to "<<decorate_slave_header<<"\n";
       break;
      }
    }else{
      filename_vec.push_back(argv[arg]);
    }
    ++arg;
  }
  file_args = filename_vec.size();
  if (file_args<2){
    cerr<<"You specified "<<file_args<<" files\n";
    cerr<<"Must have at least two input files.\n";
    exit(1);
  }
  cerr<<"Total files are "<<file_args<<endl;
  headers = new string[file_args];
  cerr<<"Using key of dimension "<<key_dim<<endl;
  for(int i=0;i<key_dim;++i) active_key.push_back("");
  masterfile=filename_vec[0];
  long int r = time(NULL);
  ostringstream oss;
  //oss<<"./"<<r;
  oss<<"/dev/shm/"<<r;
  ran = oss.str().data();
  cerr<<"Tempfile prefix: "<<ran<<endl;
  load_master(masterfile.data());
  slave_data = new string[totalrows];
  int slaves = 0;
  for(int a=1;a<file_args;++a){
    for(int i=0;i<totalrows;++i) slave_data[i]="";
    const char * slavefile=filename_vec[a].data();
    cerr<<"Reading slave file "<<slavefile<<endl;
    int fields=0;
    load_slave(slaves,slavefile,fields);
    print_padded(fields,slaves);
    ++slaves;
  }
  paste_all();
  cerr<<"Total run time in "<<(clock()-start)/CLOCKS_PER_SEC<<" seconds.\n";
  return 0;
}
